import {DeviceTabs} from '@site/src/react-luma';
import {HelloCubeExample} from '@site/src/examples';

# Hello Cube

This tutorial demonstrates how to render a spinning textured cube using luma.gl's cross-platform rendering APIs.

:::caution
Tutorials are maintained on a best-effort basis and may not be fully up to date (contributions welcome). Working versions of these examples are found in the [`/examples/tutorials`](https://github.com/visgl/luma.gl/tree/master/examples/tutorials) directory of the luma.gl repository.
:::

<DeviceTabs />
<HelloCubeExample />

It is assumed you've set up your development environment as described in [Setup](/docs/tutorials).

The cube shaders sample from a 2D texture and apply a model-view-projection
matrix. A `UniformStore` tracks that matrix so it can be updated every frame as
the cube rotates. The texture is loaded asynchronously and bound through the
model's `bindings` property along with the uniform buffer.

The complete source for this example is shown below. It builds a `Model` with WGSL
and GLSL shaders, manages uniforms with a `UniformStore`, and draws a `CubeGeometry`
inside an `AnimationLoopTemplate`.

```typescript
import type {NumberArray, VariableShaderType} from '@luma.gl/core';
import {Texture, UniformStore} from '@luma.gl/core';
import {
  AnimationLoopTemplate,
  makeAnimationLoop,
  type AnimationProps,
  Model,
  CubeGeometry,
  loadImageBitmap,
  AsyncTexture
} from '@luma.gl/engine';
import {webgl2Adapter} from '@luma.gl/webgl';
import {webgpuAdapter} from '@luma.gl/webgpu';

import {Matrix4} from '@math.gl/core';

const WGSL_SHADER = /* WGSL */ `
struct Uniforms {
  modelViewProjectionMatrix : mat4x4<f32>,
};

@group(0) @binding(0) var<uniform> app : Uniforms;
@group(0) @binding(1) var uTexture : texture_2d<f32>;
@group(0) @binding(2) var uTextureSampler : sampler;

struct VertexInputs {
  @location(0) positions : vec4<f32>,
  @location(1) texCoords : vec2<f32>
};

struct FragmentInputs {
  @builtin(position) Position : vec4<f32>,
  @location(0) fragUV : vec2<f32>,
  @location(1) fragPosition: vec4<f32>,
}

@vertex
fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
  var outputs : FragmentInputs;
  outputs.Position = app.modelViewProjectionMatrix * inputs.positions;
  outputs.fragUV = inputs.texCoords;
  outputs.fragPosition = 0.5 * (inputs.positions + vec4(1.0, 1.0, 1.0, 1.0));
  return outputs;
}

@fragment
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
  return textureSample(uTexture, uTextureSampler, inputs.fragUV);
}
`;

// GLSL
const VS_GLSL = /* glsl */ `
#version 300 es
#define SHADER_NAME cube-vs

uniform appUniforms {
  mat4 modelViewProjectionMatrix;
} app;

layout(location=0) in vec3 positions;
layout(location=1) in vec2 texCoords;

out vec2 fragUV;
out vec4 fragPosition;

void main() {
  gl_Position = app.modelViewProjectionMatrix * vec4(positions, 1.0);
  fragUV = texCoords;
  fragPosition = 0.5 * (vec4(positions, 1.) + vec4(1., 1., 1., 1.));
}
`;

const FS_GLSL = /* glsl */ `
#version 300 es
#define SHADER_NAME cube-fs
precision highp float;

uniform sampler2D uTexture;

in vec2 fragUV;
in vec4 fragPosition;

layout (location=0) out vec4 fragColor;

void main() {
  fragColor = texture(uTexture, vec2(fragUV.x, 1.0 - fragUV.y));
}
`;

type AppUniforms = {
  mvpMatrix: NumberArray;
};

const app: {uniformTypes: Record<keyof AppUniforms, VariableShaderType>} = {
  uniformTypes: {
    mvpMatrix: 'mat4x4<f32>'
  }
};

const eyePosition = [0, 0, -4];

class AppAnimationLoopTemplate extends AnimationLoopTemplate {
  mvpMatrix = new Matrix4();
  viewMatrix = new Matrix4().lookAt({eye: eyePosition});
  model: Model;
  uniformStore = new UniformStore<{app: AppUniforms}>({app});

  constructor({device}: AnimationProps) {
    super();

    const texture = new AsyncTexture(device, {
      usage: Texture.TEXTURE | Texture.RENDER_ATTACHMENT | Texture.COPY_DST,
      data: loadImageBitmap('vis-logo.png'),
      flipY: true,
      mipmaps: true,
      sampler: device.createSampler({
        minFilter: 'linear',
        magFilter: 'linear',
        mipmapFilter: 'linear'
      })
    });

    this.model = new Model(device, {
      id: 'rotating-cube',
      source: WGSL_SHADER,
      vs: VS_GLSL,
      fs: FS_GLSL,
      geometry: new CubeGeometry({indices: false}),
      bindings: {
        app: this.uniformStore.getManagedUniformBuffer(device, 'app'),
        uTexture: texture
      },
      parameters: {
        depthWriteEnabled: true,
        depthCompare: 'less-equal'
      }
    });
  }

  onFinalize() {
    this.model.destroy();
    this.uniformStore.destroy();
  }

  onRender({device, aspect, tick}: AnimationProps) {
    this.mvpMatrix
      .perspective({fovy: Math.PI / 3, aspect})
      .multiplyRight(this.viewMatrix)
      .rotateX(tick * 0.01)
      .rotateY(tick * 0.013);

    this.uniformStore.setUniforms({
      app: {mvpMatrix: this.mvpMatrix}
    });

    const renderPass = device.beginRenderPass({clearColor: [0, 0, 0, 1], clearDepth: 1});
    this.model.draw(renderPass);
    renderPass.end();
  }
}

const animationLoop = makeAnimationLoop(AnimationLoopTemplate, {adapters: [webgpuAdapter, webgl2Adapter]})
animationLoop.start();
```

During rendering the animation loop recalculates the matrix, updates the uniform
buffer and draws the cube inside a render pass. The fragment shader samples the
bound texture to shade each face.

Running the application will display a rotating cube textured with the vis.gl logo.
